﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BricksProblemSolver
{
    public abstract class Solver<T> : ISolver<T>
        where T : class, IBrickRow
    {
        public Solver(int maxLength, int maxHeight, sbyte[] brickTypes, ILog log)
        {
            if (maxLength <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(maxLength), "maxLength should above 0.");
            }
            if (maxHeight <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(maxHeight), "maxHeight should above 0.");
            }
            if (brickTypes == null || !brickTypes.Any())
            {
                throw new ArgumentNullException(nameof(brickTypes));
            }
            if (brickTypes.Any(x => x == 0))
            {
                throw new ArgumentOutOfRangeException(nameof(brickTypes), "all brickTypes should above 0.");
            }
            MaxLength = maxLength;
            MaxHeight = maxHeight;
            BrickTypes = brickTypes.OrderBy(x => x).ToArray();
            Log = log;
        }
        public int MaxLength
        {
            get; private set;
        }

        public int MaxHeight
        {
            get; private set;
        }

        public sbyte[] BrickTypes
        {
            get; private set;
        }

        public ILog Log
        {
            get; private set;
        }

        public IEnumerable<BrickWall<T>> Solve()
        {
            long count = 0;
            var finnalList = new List<BrickWall<T>>();
            var notMatchDic = GetNotMatchDicOfRows();
            var initialWall = new BrickWall<T>(MaxHeight);
            SetNextRow(0, initialWall, ref notMatchDic, ref finnalList, ref count);
            return finnalList;
        }

        public long SolveCount()
        {
            var notMatchDic = GetNotMatchDicOfRows().ToDictionary(x => (IBrickRow)x.Key, x => x.Value.Cast<IBrickRow>().ToArray());
            var countSolver = new SolutionsCountSolver(notMatchDic, MaxHeight, Log);
            return countSolver.Solve();
        }

        public IEnumerable<T> SolveOneRow()
        {
            var initialRow = CreateRow();
            var results = new List<T>();
            SetNextBrick(ref initialRow, ref results);
            return results;
        }

        private Dictionary<T, T[]> GetNotMatchDicOfRows()
        {
            IEnumerable<T> oneRowResults;
            using (Log.LogPerformance("Solve one row."))
            {
                oneRowResults = SolveOneRow();
            }
            using (Log.LogPerformance("Create not match dictionary for rows. Count: {0}.", oneRowResults.Count()))
            {
                return oneRowResults.ToDictionary(x => x, x => oneRowResults.Where(y => y.HasNoGapOnSameLine(x)).ToArray());
            }
        }

        private void SetNextBrick(ref T row, ref List<T> results)
        {
            for (int i = 0; i < BrickTypes.Length; ++i)
            {
                if (row.CanAddBrick(BrickTypes[i]))
                {
                    var cloneRow = row.CloneRow();
                    cloneRow.AddBrick(BrickTypes[i]);
                    if (cloneRow.IsFull)
                    {
                        results.Add(cloneRow);
                    }
                    else
                    {
                        SetNextBrick(ref cloneRow, ref results);
                    }
                }
            }
        }

        private void SetNextRow(int rowIndex,
            BrickWall<T> wall,
            ref Dictionary<T, T[]> brickRowNotMatchDic,
            ref List<BrickWall<T>> finnalResult,
            ref long count)
        {
            var rows = rowIndex == 0 ? brickRowNotMatchDic.Keys.ToArray() : brickRowNotMatchDic[wall[rowIndex - 1]];
            for (int i = 0; i < rows.Length; ++i)
            {
                wall[rowIndex] = rows[i];
                if (rowIndex == wall.Height - 1)
                {
                    if (finnalResult != null)
                    {
                        finnalResult.Add(wall.Clone() as BrickWall<T>);
                    }
                    ++count;
                }
                else
                {
                    SetNextRow(rowIndex + 1, wall, ref brickRowNotMatchDic, ref finnalResult, ref count);
                }
            }
        }

        protected abstract T CreateRow();
    }
}
